﻿using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

namespace Learning
{
    public static class TerrainBake
    {
        [MenuItem("Terrain/Terrain Bake")]
        private static void TerrainBakeMenu()
        {
            var gameObjects = Selection.gameObjects;
            BakeTerrain(gameObjects, 2048, "Assets/Learning/TerrainBake/Res/Terrain/Terrain_0_0_1_data_Mesh/textures");
        }
        
        /// <summary>
        /// 开始烘焙指定地形
        /// 注意这里的地形并不是 Unity 自带的 Terrain 而是已经转换后的 TerrainMesh，即已经是普通的Mesh+材质渲染的形式的地形
        /// </summary>
        /// <param name="terrainGO">需要烘焙的地形</param>
        /// <param name="textureSize">烘焙后的地块大小</param>
        /// <param name="outputPath">新生成纹理存放路径</param>
        public static void BakeTerrain(IList<GameObject> terrainGOs, int textureSize, string outputPath)
        {
            var renderTexture = new RenderTexture(textureSize, textureSize, 0);
            var texture = new Texture2D(textureSize, textureSize);
            
            // 创建用来进行烘焙使用的材质
            var bakeShader = Shader.Find("RK_Custom_Terrain_4Layers_bake");
            var bakeMat = new Material(bakeShader);
            
            var commandBuffer = CommandBufferPool.Get();
            
            foreach (var terrainGO in terrainGOs)
            {
                var mesh = terrainGO.GetComponent<MeshFilter>().sharedMesh;
                var mat = terrainGO.GetComponent<Renderer>().sharedMaterial;
                var bounds = terrainGO.GetComponent<Renderer>().bounds;
                
                CopyMatPropertyes(mat, bakeMat);
                bakeMat.shader = bakeShader;
                
                // 下面执行烘焙的渲染流程
                renderTexture.DiscardContents();
                Bake(commandBuffer, renderTexture, bounds, mesh, bakeMat);
                
                // 创建渲染好的贴图
                var path = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(terrainGO)) + "_blended.png");
                var blendTexture = CreateBlendTexture(renderTexture, texture, path);
                // 把混合后的贴图赋值给原来的地形材质
                SetBlendTexture(mat, blendTexture);
            }
            
            CommandBufferPool.Release(commandBuffer);
        }

        private static void CopyMatPropertyes(Material source, Material target)
        {
            foreach (string property in source.GetPropertyNames(MaterialPropertyType.Texture))
            {
                target.SetTexture(property, source.GetTexture(property));
            }
            foreach (string property in source.GetPropertyNames(MaterialPropertyType.Vector))
            {
                target.SetVector(property, source.GetVector(property));
            }
            foreach (string property in source.GetPropertyNames(MaterialPropertyType.Float))
            {
                target.SetFloat(property, source.GetFloat(property));
            }
            foreach (string property in source.GetPropertyNames(MaterialPropertyType.Int))
            {
                target.SetInt(property, source.GetInt(property));
            }
        }

        private static void Bake(CommandBuffer commandBuffer, RenderTexture renderTexture, Bounds bounds, Mesh mesh, Material mat)
        {
            commandBuffer.Clear();
            
            commandBuffer.SetRenderTarget(renderTexture, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
            commandBuffer.ClearRenderTarget(true, true, Color.clear);
            
            // 渲染使用的正交相机矩阵需要的关键参数，可以理解为摄像机的位置和相机的size
            var center = bounds.center;
            center.y += 500;
            var orthoSize = bounds.extents.x;
            // 创建正交相机的矩阵并设置
            var nearClipPlane = 0.1f;
            var farClipPlane = 10000;
            var projectionMatrix = Matrix4x4.Ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearClipPlane, farClipPlane);
            var viewMatrix = Matrix4x4.TRS(center, Quaternion.Euler(90, 0, 0), Vector3.one).inverse; // 注意这里要使用的是逆矩阵
            if (SystemInfo.usesReversedZBuffer)
            {
                viewMatrix.m20 = -viewMatrix.m20;
                viewMatrix.m21 = -viewMatrix.m21;
                viewMatrix.m22 = -viewMatrix.m22;
                viewMatrix.m23 = -viewMatrix.m23;
            }
            commandBuffer.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
            
            // 设定光源方向为场景主光源的方向，注意这里要取反
            Vector3 lightDir = -RenderSettings.sun.transform.forward;
            commandBuffer.SetGlobalVector("lightDir", lightDir);
            
            // 渲染地块 Mesh
            commandBuffer.DrawMesh(mesh, Matrix4x4.identity, mat);
            Graphics.ExecuteCommandBuffer(commandBuffer);
        }

        private static Texture2D CreateBlendTexture(RenderTexture renderTexture, Texture2D texture, string path)
        {
            RenderTexture.active = renderTexture;
            texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
            texture.Apply();
            var bytes = texture.EncodeToPNG();
            File.WriteAllBytes(path, bytes);
            AssetDatabase.ImportAsset(path);
            return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
        }
        
        private static void SetBlendTexture(Material mat, Texture2D blendTexture)
        {
            mat.SetTexture("_Combined", blendTexture);
            EditorUtility.SetDirty(mat);
            AssetDatabase.SaveAssets();
        }
    }
}
